package com.chorye.manualtransactional.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chorye.manualtransactional.dao.StudentMapper;
import com.chorye.manualtransactional.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.math.BigDecimal;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Chorye
 * @since 2019-06-14
 */
@Service
public class StudentService extends ServiceImpl<StudentMapper, Student> {
    /**
     * 日志记录
     */
    private final Logger log = LoggerFactory.getLogger(StudentService.class);
    /**
     * 事务管理器
     */
    @Autowired
    DataSourceTransactionManager dataSourceTransactionManager;
    /**
     * 事务定义信息
     */
    @Autowired
    DefaultTransactionDefinition transactionDefinition;
    /**
     * 学生数据层
     */
    @Autowired
    private StudentMapper studentMapper;

    /**
     * 事务TransactionStatus的一些API
     * 1、isNewTransaction：返回当前事务是否是新的; 否则使用现有事务
     * 2、setRollbackOnly：设置仅事务回滚。这指示事务管理器事务的唯一可能结果可能是回滚，作为抛出异常的替代方法，而异常又会触发回滚。
     * 3、isRollbackOnly：返回事务是否已标记为仅回滚
     * 4、isCompleted：返回此事务是否已完成，即是否已提交或回滚
     */
    public void manualZero() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启事务成功");
        log.info("事务是否已提交或回滚：{}", transactionStatus.isCompleted());
        log.info("当前事务是否是新的：{}", transactionStatus.isNewTransaction());
        log.info("返回事务是否已标记为仅回滚：{}", transactionStatus.isRollbackOnly());
        try {

            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus.isRollbackOnly());

        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("手动回滚事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus.isRollbackOnly());
        }

//        手动开启事务成功
//        事务是否已提交或回滚：false
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：false
//        手动提交事务成功
//        事务是否已提交或回滚：true
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：false

        log.info("++++++++++++++++++++++++++++++++++++++");

        // 手动开启事务
        TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启事务成功");
        log.info("事务是否已提交或回滚：{}", transactionStatus1.isCompleted());
        log.info("当前事务是否是新的：{}", transactionStatus1.isNewTransaction());
        log.info("返回事务是否已标记为仅回滚：{}", transactionStatus1.isRollbackOnly());
        try {
            int i = 1 / 0;
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus1);
            log.info("手动提交事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus1.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus1.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus1.isRollbackOnly());

        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus1);
            log.info("手动回滚事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus1.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus1.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus1.isRollbackOnly());
        }

//        手动开启事务成功
//        事务是否已提交或回滚：false
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：false
//        手动回滚事务成功
//        事务是否已提交或回滚：true
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：false

        log.info("++++++++++++++++++++++++++++++++++++++");

        // 手动开启事务
        TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启事务成功");
        log.info("事务是否已提交或回滚：{}", transactionStatus.isCompleted());
        log.info("当前事务是否是新的：{}", transactionStatus.isNewTransaction());
        log.info("返回事务是否已标记为仅回滚：{}", transactionStatus.isRollbackOnly());
        try {
            int i = 1 / 0;
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus2);
            log.info("手动提交事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus2.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus2.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus2.isRollbackOnly());

        } catch (Exception e) {
            // 手动回滚事务
            transactionStatus2.setRollbackOnly();
            log.info("手动回滚事务成功");
            log.info("事务是否已提交或回滚：{}", transactionStatus2.isCompleted());
            log.info("当前事务是否是新的：{}", transactionStatus2.isNewTransaction());
            log.info("返回事务是否已标记为仅回滚：{}", transactionStatus2.isRollbackOnly());
        }

//        手动开启事务成功
//        事务是否已提交或回滚：true
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：false
//        手动回滚事务成功
//        事务是否已提交或回滚：false
//        当前事务是否是新的：true
//        返回事务是否已标记为仅回滚：true

    }

    /**
     * 场景一：
     * 一个库，一个方法，一个事务，两个操作，正常提交事务
     * 结果：
     * 都进去了
     * =================
     * 手动开启事务成功
     * 手动提交事务成功
     */
    public void manualOne() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启事务成功");
        try {

            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);

            Student student2 = new Student();
            student2.setName("大脸猫");
            student2.setAddress("动画城");
            student2.setAge(18);
            student2.setMobile("13555555555");
            student2.setSalary(BigDecimal.valueOf(12345));
            studentMapper.insert(student);

            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交事务成功");

        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("手动回滚事务成功");
        }
    }

    /**
     * 场景二：
     * 一个库，一个方法，一个事务，两个操作，第二个抛出异常，无法提交事务
     * 结果：
     * 事务回滚，一条都没进去
     * =================
     * 手动开启事务成功
     * 手动回滚事务成功
     */
    public void manualTwo() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启事务成功");
        try {

            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);

            int i = 1 / 0;

            Student student2 = new Student();
            student2.setName("大脸猫");
            student2.setAddress("动画城");
            student2.setAge(18);
            student2.setMobile("13555555555");
            student2.setSalary(BigDecimal.valueOf(12345));
            studentMapper.insert(student);

            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("手动回滚事务成功");
        }
    }


    /**
     * 场景三：
     * 一个库，一个方法，两个事务，第一个成功提交事务，第二个去数据库查第一个的结果，然后修改，第二个成功提交事务
     * 结果：
     * 第一个事务提交入库后第二个事务查询到结果，并修改提交事务，数据库只有一条记录
     * =================
     * 手动开启新增事务成功
     * 手动提交新增事务成功
     * 手动开启查询修改事务成功
     * 手动回滚查询修改事务成功
     */
    public void manualThree() {
        // 手动开启事务
        TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启新增事务成功");
        try {

            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus1);
            log.info("手动提交新增事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus1);
            log.info("手动回滚新增事务成功");
        }
        // 手动开启事务
        TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启查询修改事务成功");
        try {

            QueryWrapper<Student> condition = new QueryWrapper<>();
            condition.eq("name", "蓝皮鼠");
            Student student = studentMapper.selectOne(condition);
            student.setName("大脸猫");
            studentMapper.updateById(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus2);
            log.info("手动提交查询修改事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus2);
            log.info("手动回滚查询修改事务成功");
        }
    }

    /**
     * 场景四：
     * 一个库，一个方法，两个事务，第一个成功提交事务，第二个去数据库查第一个的结果，然后修改，修改中报错，第二个无法提交事务
     * 结果：
     * 第一个事务成功提交数据到数据库，第二个事务中抛出的异常并没有影响到第一个事务的提交入库
     * =================
     * 手动开启新增事务成功
     * 手动提交新增事务成功
     * 手动开启查询修改事务成功
     * 手动回滚查询修改事务成功
     */
    public void manualFour() {
        // 手动开启事务
        TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启新增事务成功");
        try {

            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus1);
            log.info("手动提交新增事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus1);
            log.info("手动回滚新增事务成功");
        }
        // 手动开启事务
        TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启查询修改事务成功");
        try {

            QueryWrapper<Student> condition = new QueryWrapper<>();
            condition.eq("name", "蓝皮鼠");
            Student student = studentMapper.selectOne(condition);
            student.setName("大脸猫");

            int i = 1 / 0;

            studentMapper.updateById(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus2);
            log.info("手动提交查询修改事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus2);
            log.info("手动回滚查询修改事务成功");
        }
    }

    /**
     * 场景五：
     * 一个库，一个方法，两个事务。第一个新增，新增中报错，无法提交事务，第二个去数据库查第一个的结果，然后修改，第二个成功提交事务
     * 结果：
     * 第一个事务失败，数据回滚，第二个事务中没有查到，所以不进行操作，事务依旧提交成功
     * =================
     * 手动开启新增事务成功
     * 手动回滚新增事务成功
     * 手动开启查询修改事务成功
     * 手动提交查询修改事务成功
     */
    public void manualFive() {
        // 手动开启事务
        TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启新增事务成功");
        try {

            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);
            int i = 1 / 0;
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus1);
            log.info("手动提交新增事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus1);
            log.info("手动回滚新增事务成功");
        }
        // 手动开启事务
        TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启查询修改事务成功");
        try {

            QueryWrapper<Student> condition = new QueryWrapper<>();
            condition.eq("name", "蓝皮鼠");
            Student student = studentMapper.selectOne(condition);
            if (student != null) {
                student.setName("大脸猫");
                studentMapper.updateById(student);
            }
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus2);
            log.info("手动提交查询修改事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus2);
            log.info("手动回滚查询修改事务成功");
        }
    }

    /**
     * 场景六：
     * 一个库，一个方法，三个事务，第一个成功提交事务，第二个去数据库查第一个的结果，然后修改，修改中报错，第二个无法提交事务，但是两个事务被包含在了一个大的事务中，每个事务都有自己的提交、回滚
     * 结果：
     * 因为事务2已经出发回滚，整个事务就结束了，此时外层事务还想回滚已经不行了
     * Transaction is already completed - do not call commit or rollback more than once per transaction
     * =================
     * 手动开启外层事务成功
     * 手动提交新增事务成功
     * 手动开启查询修改事务成功
     * 手动回滚查询修改事务成功
     * 准备手动回滚外层事务
     * org.springframework.transaction.IllegalTransactionStateException: Transaction is already completed - do not call commit or rollback more than once per transaction
     */
    public void manualSix() {

        // 手动开启事务
        TransactionStatus transactionStatus0 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            // 手动开启事务
            TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
            log.info("手动开启外层事务成功");
            try {

                Student student = new Student();
                student.setName("蓝皮鼠");
                student.setAddress("动画城");
                student.setAge(13);
                student.setMobile("13800000000");
                student.setSalary(BigDecimal.valueOf(9999.99));
                studentMapper.insert(student);
                // 手动提交事务
                dataSourceTransactionManager.commit(transactionStatus1);
                log.info("手动提交新增事务成功");
            } catch (Exception e) {
                // 手动回滚事务
                dataSourceTransactionManager.rollback(transactionStatus1);
                log.info("手动回滚新增事务成功");
            }
            // 手动开启事务
            TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
            log.info("手动开启查询修改事务成功");
            try {

                QueryWrapper<Student> condition = new QueryWrapper<>();
                condition.eq("name", "蓝皮鼠");
                Student student = studentMapper.selectOne(condition);
                student.setName("大脸猫");

                int i = 1 / 0;

                studentMapper.updateById(student);
                // 手动提交事务
                dataSourceTransactionManager.commit(transactionStatus2);
                log.info("手动提交查询修改事务成功");
            } catch (Exception e) {
                // 手动回滚事务
                dataSourceTransactionManager.rollback(transactionStatus2);
                log.info("手动回滚查询修改事务成功");
            }
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus0);
            log.info("手动提交外层事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            log.info("准备手动回滚外层事务");
            dataSourceTransactionManager.rollback(transactionStatus0);
            log.info("手动回滚外层事务成功");
        }
    }

    /**
     * 场景七：
     * 一个库，一个方法，三个事务，第一个成功提交事务，第二个去数据库查第一个的结果，然后修改，修改中报错，第二个无法提交事务，但是两个事务被包含在了一个大的事务中，只有外层事务才有回滚，内层事务抛出异常
     * 结果：
     * 事务成功回滚，新增并没有成功，即使新增已经提交了自己的事务
     * ======================
     * 手动开启外层事务成功
     * 手动提交新增事务成功
     * 手动开启查询修改事务成功
     * 查询修改事务抛出异常
     * 手动回滚外层事务成功
     */
    public void manualSeven() {

        // 手动开启事务
        TransactionStatus transactionStatus0 = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            // 手动开启事务
            TransactionStatus transactionStatus1 = dataSourceTransactionManager.getTransaction(transactionDefinition);
            log.info("手动开启外层事务成功");
            try {

                Student student = new Student();
                student.setName("蓝皮鼠");
                student.setAddress("动画城");
                student.setAge(13);
                student.setMobile("13800000000");
                student.setSalary(BigDecimal.valueOf(9999.99));
                studentMapper.insert(student);
                // 手动提交事务
                dataSourceTransactionManager.commit(transactionStatus1);
                log.info("手动提交新增事务成功");
            } catch (Exception e) {
                log.info("新增事务抛出异常");
                throw new RuntimeException();
            }
            // 手动开启事务
            TransactionStatus transactionStatus2 = dataSourceTransactionManager.getTransaction(transactionDefinition);
            log.info("手动开启查询修改事务成功");
            try {

                QueryWrapper<Student> condition = new QueryWrapper<>();
                condition.eq("name", "蓝皮鼠");
                Student student = studentMapper.selectOne(condition);
                student.setName("大脸猫");
                studentMapper.updateById(student);

                int i = 1 / 0;

                // 手动提交事务
                dataSourceTransactionManager.commit(transactionStatus2);
                log.info("手动提交查询修改事务成功");
            } catch (Exception e) {
                log.info("查询修改事务抛出异常");
                throw new RuntimeException();
            }
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus0);
            log.info("手动提交外层事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus0);
            log.info("手动回滚外层事务成功");
        }
    }

    /**
     * 场景八：
     * 一个库，一个方法，两个事务，两个操作，提交事务
     * 结果：同场景一
     * ==================
     * 手动开启外层事务成功
     * 手动提交外层事务成功
     */
    public void manualEight() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启外层事务成功");
        try {
            method1();
            method2();
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交外层事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("手动回滚外层事务成功");
        }
    }

    private void method1() {
        Student student = new Student();
        student.setName("蓝皮鼠");
        student.setAddress("动画城");
        student.setAge(13);
        student.setMobile("13800000000");
        student.setSalary(BigDecimal.valueOf(9999.99));
        studentMapper.insert(student);
    }

    private void method2() {
        Student student2 = new Student();
        student2.setName("大脸猫");
        student2.setAddress("动画城");
        student2.setAge(18);
        student2.setMobile("13555555555");
        student2.setSalary(BigDecimal.valueOf(12345));
        studentMapper.insert(student2);
    }

    /**
     * 场景九：
     * 一个库，一个方法，两个事务，提交事务
     * 结果：同场景四
     * ==================
     * 手动开启外层事务成功
     * 手动开启新增事务成功
     * 手动提交新增事务成功
     * 手动开启查询修改事务成功
     * 查询修改手动抛出异常
     * 手动回滚外层事务成功
     */
    public void manualNine() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启外层事务成功");
        try {
            method3();
            method4();
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交外层事务成功");
        } catch (Exception e) {
            // 手动回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("手动回滚外层事务成功");
        }
    }

    private void method3() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启新增事务成功");
        try {
            Student student = new Student();
            student.setName("蓝皮鼠");
            student.setAddress("动画城");
            student.setAge(13);
            student.setMobile("13800000000");
            student.setSalary(BigDecimal.valueOf(9999.99));
            studentMapper.insert(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交新增事务成功");
        } catch (Exception e) {
            // 手动抛出异常
            log.info("新增手动抛出异常");
            throw new RuntimeException();
        }
    }

    private void method4() {
        // 手动开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("手动开启查询修改事务成功");
        try {
            QueryWrapper<Student> condition = new QueryWrapper<>();
            condition.eq("name", "蓝皮鼠");
            Student student = studentMapper.selectOne(condition);
            if (student != null) {
                student.setName("大脸猫");
            }
            int i = 1 / 0;
            studentMapper.updateById(student);
            // 手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("手动提交查询修改事务成功");
        } catch (Exception e) {
            // 手动抛出异常
            log.info("查询修改手动抛出异常");
            throw new RuntimeException();
        }
    }

}
